# Функциональная обработка ошибок

Функциональное программирование похоже на написание ряда алгебраических уравнений, 
и поскольку алгебра не имеет `null` значений или исключений, эти функции не используются в ФП. 
Это поднимает интересный вопрос: 
что делать в ситуациях, когда в коде ООП обычно используются `null` значение или исключение?

Решение Scala заключается в использовании конструкций, подобных классам `Option`/`Some`/`None`. 

Примечание:

- классы `Some` и `None` являются подклассами `Option`
- вместо того чтобы многократно повторять "`Option`/`Some`/`None`", 
следующий текст обычно просто ссылается на "`Option`" или на "классы `Option`"

### Первый пример

Хотя этот первый пример не имеет дело с `null` значениями, это хороший способ познакомиться с классами `Option`.

Представим, что нужно написать метод, который упрощает преобразование строк в целочисленные значения. 
И нужен элегантный способ обработки исключения, которое возникает, когда метод получает строку типа `"Hello"` вместо `"1"`. 
Первое предположение о таком методе может выглядеть следующим образом:

```dotty
def makeInt(s: String): Int =
  try
    Integer.parseInt(s.trim)
  catch
    case e: Exception => 0
```

Если преобразование работает, метод возвращает правильное значение `Int`, но в случае сбоя метод возвращает `0`. 
Для некоторых целей это может быть хорошо, но не совсем точно. 
Например, метод мог получить `"0"`, но мог также получить `"foo"`, `"bar"` 
или бесконечное количество других строк, которые выдадут исключение. 
Это реальная проблема: как определить, когда метод действительно получил `"0"`, а когда получил что-то еще? 
При таком подходе нет способа узнать правильный ответ наверняка.

### Использование Option/Some/None

Распространенным решением этой проблемы в Scala является использование классов, известных как `Option`, `Some` и `None`. 
Классы `Some` и `None` являются подклассами `Option`, поэтому решение работает следующим образом:

- объявляется, что `makeInt` возвращает тип `Option`
- если `makeInt` получает строку, которую он может преобразовать в `Int`, ответ помещается внутрь `Some`
- если `makeInt` получает строку, которую не может преобразовать, то возвращает `None`

Вот доработанная версия `makeInt`:

```dotty
def makeInt(s: String): Option[Int] =
  try
    Some(Integer.parseInt(s.trim))
  catch
    case e: Exception => None
```

Этот код можно прочитать следующим образом: 
"Когда данная строка преобразуется в целое число, верните значение `Int`, заключенное в `Some`, например `Some(1)`. 
Когда строка не может быть преобразована в целое число и генерируется исключение, метод возвращает значение `None`."

Эти примеры показывают, как работает `makeInt`:

```dotty
val a = makeInt("1")  
// a: Option[Int] = Some(value = 1)  
val b = makeInt("one")
// b: Option[Int] = None
```

Как показано, строка `"1"` приводится к `Some(1)`, а строка `"one"` - к `None`. 
В этом суть альтернативного подхода к обработке ошибок. 
Данная техника используется для того, чтобы методы могли возвращать значения вместо исключений. 
В других ситуациях значения `Option` также используются для замены `null` значений.

Примечание:

- этот подход используется во всех классах библиотеки Scala, а также в сторонних библиотеках Scala
- ключевым моментом примера является то, что функциональные методы не генерируют исключения; 
вместо этого они возвращают такие значения, как `Option`

### Потребитель makeInt

Теперь представим, что мы являемся потребителем метода `makeInt`. 
Известно, что он возвращает подкласс `Option[Int]`, поэтому возникает вопрос: 
как работать с этими возвращаемыми типами?

Есть два распространенных ответа, в зависимости от потребностей:

- использование `match` выражений
- использование `for` выражений

### Использование match выражений

Одним из возможных решений является использование выражения `match`:

```dotty
makeInt(x) match
  case Some(i) => println(i)
  case None => println("That didn’t work.")
```

В этом примере, если `x` можно преобразовать в `Int`, вычисляется выражение в правой части первого предложения `case`; 
если `x` не может быть преобразован в `Int`, оценивается выражение в правой части второго предложения `case`.

### Использование for выражений

Другим распространенным решением является использование выражения `for`, то есть комбинации `for`/`yield`. 
Например, представим, что необходимо преобразовать три строки в целочисленные значения, а затем сложить их. 
Решение задачи с использованием выражения `for`:

```dotty
val y = for
  a <- makeInt(stringA)
  b <- makeInt(stringB)
  c <- makeInt(stringC)
yield
  a + b + c
```

После выполнения этого выражения `y` может принять одно из двух значений:

- если _все_ три строки конвертируются в значения `Int`, `y` будет равно `Some[Int]`, 
т. е. целым числом, обернутым внутри `Some`
- если _какая-либо_ из трех строк не может быть преобразована в `Int`, `y` равен `None`

Это можно проверить на примере:

```dotty
val stringA = "1"
val stringB = "2"
val stringC = "3"
val y = for
  a <- makeInt(stringA)
  b <- makeInt(stringB)
  c <- makeInt(stringC)
yield
  a + b + c
// y: Option[Int] = Some(value = 6)  
```

Чтобы увидеть негативный кейс, достаточно изменить любую из строк на что-то, что нельзя преобразовать в целое число. 
В этом случае `y` равно `None`:

```dotty
y: Option[Int] = None
```

### Восприятие Option, как контейнера

Для лучшего восприятия `Option`, его можно представить как контейнер:

- `Some` представляет собой контейнер с одним элементом
- `None` не является контейнером, в нем ничего нет

Если предпочтительнее думать об `Option` как о ящике, то `None` подобен пустому ящику. 
Что-то в нём могло быть, но нет.

### Использование Option для замены null

Возвращаясь к значениям `null`, место, где `null` значение может незаметно проникнуть в код, — класс, подобный этому:

```dotty
class Address(
  var street1: String,
  var street2: String,
  var city: String,
  var state: String,
  var zip: String
)
```

Хотя каждый адрес имеет значение `street1`, значение `street2` не является обязательным. 
В результате полю `street2` можно присвоить значение `null`:

```dotty
val santa = Address(
  "1 Main Street",
  null,            
  "North Pole",
  "Alaska",
  "99705"
)
```

Исторически сложилось так, что в этой ситуации разработчики использовали пустые строки и значения `null`, 
оба из которых "взламывают" решения основной проблемы: `street2` - необязательное поле. 
В Scala и других современных языках правильное решение состоит в том, 
чтобы заранее объявить, что `street2` является необязательным:

```dotty
class Address(
  var street1: String,
  var street2: Option[String],  
  var city: String,
  var state: String,
  var zip: String
)
```

Теперь можно написать более точный код:

```dotty
val santa = Address(
  "1 Main Street",
  None,         
  "North Pole",
  "Alaska",
  "99705"
)
```

или так:

```dotty
val santa = Address(
  "123 Main Street",
  Some("Apt. 2B"),
  "Talkeetna",
  "Alaska",
  "99676"
)
```

### Option — не единственное решение

В этом разделе основное внимание уделялось `Option` классам, но у Scala есть несколько других альтернатив.

Например, три класса, известные как `Try`/`Success`/`Failure`, работают также, 
но (а) эти классы в основном используются, когда код может генерировать исключения, 
и (б) когда желательно использовать класс `Failure`, потому что он дает доступ к сообщению об исключении. 
Например, классы `Try` обычно используются при написании методов, 
которые взаимодействуют с файлами, базами данных или интернет-службами, 
поскольку эти функции могут легко создавать исключения.

### Краткое ревью

- функциональные программисты не используют `null` значения
- основной заменой `null` значениям является использование классов `Option`
- функциональные методы не выдают исключений; вместо этого они возвращают такие значения, как `Option`, `Try` или `Either`
- распространенными способами работы со значениями `Option` являются выражения `match` и `for`
- `Option` можно рассматривать как контейнеры с одним элементом (`Some`) и без элементов (`None`)
- `Option` также можно использовать для дополнительных параметров конструктора или метода


---

**Ссылки:**

- [Scala3 book](https://docs.scala-lang.org/scala3/book/fp-functional-error-handling.html)
- [I hate NULL and all its variants!](https://alexn.org/blog/2010/05/25/i-hate-null/)
- [Making something out of nothing (or, why None is better than NaN and NULL)](https://blog.janestreet.com/making-something-out-of-nothing-or-why-none-is-better-than-nan-and-null/)
- [Null, NullPointerException and dealing with it](https://scalac.io/blog/null-pointer-exception-npe/)
- [Null References: The Billion Dollar Mistake](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)
